import { PL00ParserVisitor } from "../antlr/PL00ParserVisitor";
import { PL00SymbolTable } from "./PL00SymbolTable";
import {
  Create,
  ProcedureSymbol,
  FunctionSymbol,
  FunctionArgument,
  IntegerSymbol,
  StringSymbol,
  RealSymbol,
} from "./PL00Symbols";
import { ParserRuleContext } from "../../node_modules/antlr4/index.js";
import BuiltinsFactory from "./PL00BuiltInFunctions";
import { PL00SymbolCalculator } from "./PL00SymbolCalculator";
import { importModuleFactory } from "./PL00Import";
import { PL00Error } from "src/app/pl00-editor/PL00Error";

/**
 * 生成PL00Error的工具函数
 * @param ctx 发生错误的上下文
 * @param msg 错误消息
 */
function getError(ctx: ParserRuleContext, msg: string): PL00Error {
  if (ctx === undefined) {
    return new PL00Error(0, 0, 0, 0, msg);
  }
  if ("symbol" in ctx) {
    return new PL00Error(
      ctx.symbol.line,
      ctx.symbol.line,
      ctx.symbol.column + 1,
      ctx.symbol.column + ctx.symbol.text.length + 1,
      msg
    );
  }
  const tokenStart = ctx.start;
  const tokenStop = ctx.stop;
  return new PL00Error(
    tokenStart.line,
    tokenStop.line,
    tokenStart.column + 1,
    tokenStop.column + 1,
    msg
  );
}

function PL00QuickVisitorFactory(modules: {
  [moduleName: string]: { [functionName: string]: FunctionSymbol };
}) {
  const symbolTable = new PL00SymbolTable();
  symbolTable.push();
  // 不需要输入和输出
  const builtins = BuiltinsFactory(
    () => {},
    () => {}
  );
  for (const funcName in builtins) {
    for (const func of builtins[funcName]) {
      symbolTable.add(funcName, func);
    }
  }

  function PL00QuickVisitor() {
    PL00ParserVisitor.call(this);
    this.symbolTable = symbolTable;
    // 错误列表
    this.errors = [];
    return this;
  }
  PL00QuickVisitor.prototype = Object.create(PL00ParserVisitor.prototype);
  PL00QuickVisitor.prototype.constructor = PL00QuickVisitor;

  // Visit a parse tree produced by PL00Parser#program.
  PL00QuickVisitor.prototype.visitProgram = function (ctx) {
    this.visitChildren(ctx);
  };

  // Visit a parse tree produced by PL00Parser#importstmt.
  PL00QuickVisitor.prototype.visitImportstmt = function (ctx) {
    const importModule = importModuleFactory(this);
    for (let i = 0; 2 * i + 3 <= ctx.children.length; i++) {
      let moduleName = this.visitPath(ctx.children[i * 2 + 1]);
      if (moduleName in modules) {
        for (const functionName in modules[moduleName]) {
          symbolTable.add(functionName, modules[moduleName][functionName]);
        }
      } else {
        symbolTable.importModule(moduleName);
        const functions = importModule(moduleName);
        if (!(moduleName in modules)) {
          modules[moduleName] = {};
        }
        // 找不到模块的错误
        importModule(moduleName).then((functions) => {
          if (functions === null || functions === undefined) {
            this.errors.push(
              getError(ctx, "module '" + moduleName + "' not found")
            );
          } else {
            for (const func of functions) {
              modules[moduleName][func.name] = func.value;
            }
          }
        });
      }
    }
  };

  // Visit a parse tree produced by PL00Parser#path.
  PL00ParserVisitor.prototype.visitPath = function (ctx): string {
    return ctx.children.map((x) => x.symbol.text).join("");
  };

  // Visit a parse tree produced by PL00Parser#block.
  PL00QuickVisitor.prototype.visitBlock = function (ctx) {
    const children = this.visitChildren(ctx);
    const result = children[children.length - 1];
    return result;
  };

  // Visit a parse tree produced by PL00Parser#consts.
  PL00QuickVisitor.prototype.visitConsts = function (ctx) {
    const children = this.visitChildren(ctx);
    for (let i = 0; 4 * i + 5 <= children.length; i++) {
      const identName = children[4 * i + 1];
      const value = children[4 * i + 3];
      symbolTable.add(identName, value);
    }
  };

  // Visit a parse tree produced by PL00Parser#vars.
  PL00QuickVisitor.prototype.visitVars = function (ctx) {
    const children = this.visitChildren(ctx);
    let position = 1;
    let temp = [];
    while (true) {
      if (position == ctx.children.length) {
        break;
      }
      const element = ctx.children[position];
      //因为其它错误导致无法解析vars的错误
      if (element === undefined || element === null) {
        this.errors.push(getError(ctx, "can't resolve vars"));
        position++;
      }
      if ("symbol" in element) {
        if (element.symbol.text == ",") {
          position++;
        } else if (element.symbol.text == ":") {
          const typeName = children[position + 1];
          for (const name of temp) {
            symbolTable.add(name, Create(typeName));
          }
          position += 3;
          temp = [];
          continue;
        }
      }
      temp.push(children[position++]);
    }
  };

  // Visit a parse tree produced by PL00Parser#procedure.
  PL00QuickVisitor.prototype.visitProcedure = function (ctx) {
    const procedureName = this.visitIdent(ctx.children[1]);
    symbolTable.add(procedureName, new ProcedureSymbol(ctx.children[3]));
  };

  // Visit a parse tree produced by PL00Parser#functionDef.
  PL00QuickVisitor.prototype.visitFunctionDef = function (ctx) {
    const functionName = ctx.children[1].symbol.text;
    const functionSymbol = new FunctionSymbol(
      ctx.children[ctx.children.length - 2],
      this.visitType(ctx.children[ctx.children.length - 4])
    );
    for (let i = 0; i * 4 + 12 <= ctx.children.length; i++) {
      const identName = this.visitIdent(ctx.children[i * 4 + 3]);
      const typeName = this.visitIdent(ctx.children[i * 4 + 5]);
      functionSymbol.params.push(new FunctionArgument(identName, typeName));
    }
    symbolTable.add(functionName, functionSymbol);
    return { name: functionName, value: functionSymbol };
  };

  // Visit a parse tree produced by PL00Parser#functionDefs.
  PL00QuickVisitor.prototype.visitFunctionDefs = function (ctx) {
    return this.visitChildren(ctx);
  };

  // Visit a parse tree produced by PL00Parser#statement.
  PL00QuickVisitor.prototype.visitStatement = function (ctx) {
    // console.log(ctx)
    const result = this.visitChildren(ctx);
    if (result === null) {
      return null;
    }
    return result[0];
  };

  // Visit a parse tree produced by PL00Parser#assignstmt.
  PL00QuickVisitor.prototype.visitAssignstmt = function (
    ctx: ParserRuleContext
  ) {
    let children = this.visitChildren(ctx);
    let name = children[0];
    const symbol = symbolTable.get(name);
    if (symbol === null) {
      this.errors.push(
        getError(ctx.children[0], "identity '" + name + "' not declared")
      );
      return;
    }
    const element = children[2];
    if (element === null || element === undefined) {
      return;
    }
    // 赋值时类型不相同的错误
    if (symbol.type !== element.type) {
      this.errors.push(
        getError(
          ctx,
          symbol.type +
            " identity '" +
            name +
            "' cannot be assigned with " +
            element.type +
            " value"
        )
      );
    }
    symbolTable.bindValue(name, element.value);
  };

  // Visit a parse tree produced by PL00Parser#vectorassignstmt.
  PL00QuickVisitor.prototype.visitVectorassignstmt = function (ctx) {
    let children = this.visitChildren(ctx);
    let name = children[0];
    // 标识符未定义错误
    if (symbolTable.get(name) === null) {
      this.errors.push(
        getError(ctx.children[0], "identity '" + name + "' not declared")
      );
      return;
    }
    let array = symbolTable.get(name);
    // 索引赋值类型错误
    if (["Integer", "Real"].includes(array.type)) {
      this.errors.push(
        getError(
          ctx.children[0],
          "Can't get index of " + array.type + " '" + name + "'"
        )
      );
      return;
    }
  };

  // Visit a parse tree produced by PL00Parser#callstmt.
  PL00QuickVisitor.prototype.visitCallstmt = function (ctx) {
    // 这个以及函数调用的逻辑其实都是有问题的，显然语义分析应该在定义时就
    // 检测过程体/函数体，在调用时忽视调用过程，现在这种情况很容易遇到
    // 递归死循环的情况。但是懒得改了。
    const procedureName = this.visitIdent(ctx.children[1]);
    symbolTable.push();
    if (symbolTable.get(procedureName) === null) {
      this.errors.push(
        getError(ctx.children[0], "procedure '" + name + "' not declared")
      );
    } else {
      this.visitBlock(symbolTable.get(procedureName).value);
    }
    symbolTable.pop();
  };

  // Visit a parse tree produced by PL00Parser#returnstmt.
  PL00QuickVisitor.prototype.visitReturnstmt = function (ctx) {
    return { return: this.visitExpression(ctx.children[1]) };
  };

  // Visit a parse tree produced by PL00Parser#beginstmt.
  PL00QuickVisitor.prototype.visitBeginstmt = function (ctx) {
    for (let i = 0; i * 2 + 3 <= ctx.children.length; ++i) {
      const temp = this.visitStatement(ctx.children[i * 2 + 1]);
      if (temp && "return" in temp) {
        return temp.return;
      }
    }
  };

  // Visit a parse tree produced by PL00Parser#ifstmt.
  PL00QuickVisitor.prototype.visitIfstmt = function (ctx) {
    // console.log(ctx);
    const condition = this.visitCondition(ctx.children[1]);
    const statement1 = this.visitStatement(ctx.children[3]);
    // else
    if (ctx.children.length >= 5) {
      const statement2 = this.visitStatement(ctx.children[5]);
    }
    // return this.visitChildren(ctx);
  };

  // Visit a parse tree produced by PL00Parser#whilestmt.
  PL00QuickVisitor.prototype.visitWhilestmt = function (ctx) {
    // 不循环了
    const condition = this.visitCondition(ctx.children[1]);
    const statement = this.visitStatement(ctx.children[3]);
  };

  // Visit a parse tree produced by PL00Parser#breakstmt.
  PL00QuickVisitor.prototype.visitBreakstmt = function (ctx) {
    return { break: true };
  };

  // Visit a parse tree produced by PL00Parser#continuestmt.
  PL00QuickVisitor.prototype.visitContinuestmt = function (ctx) {
    return { continue: true };
  };

  // Visit a parse tree produced by PL00Parser#condition.
  PL00QuickVisitor.prototype.visitCondition = function (ctx) {
    var children = this.visitChildren(ctx);
    const op = ctx.children[1].symbol.text;
    const calculator = new PL00SymbolCalculator(this, symbolTable);
    const lhs = children[0];
    const rhs = children[2];
    // 由于错误恢复策略导致的各种判断不能
    if (lhs === null || lhs.type === null) {
      this.errors.push(getError(ctx.children[0], "cannot resolve"));
      return null;
    }
    if (rhs === null || rhs.type === null) {
      this.errors.push(getError(ctx.children[2], "cannot resolve"));
      return null;
    }
    // 比较对象类型不等的错误
    if (lhs.type !== rhs.type) {
      this.errors.push(
        getError(ctx, lhs.type + " can't compare with " + rhs.type)
      );
    }
  };

  // Visit a parse tree produced by PL00Parser#callfunction.
  PL00QuickVisitor.prototype.visitCallfunction = function (
    ctx,
    firstParam = null
  ) {
    const funcName = ctx.children[0].symbol.text;
    const params = [];
    if (firstParam !== null) {
      params.push(firstParam);
    }
    for (let i = 0; 2 * i + 4 <= ctx.children.length; i++) {
      const result = this.visitExpression(ctx.children[i * 2 + 2]);
      params.push(result);
    }
    return this.callFunction(funcName, params, ctx.children[0]);
  };

  PL00QuickVisitor.prototype.callFunction = function (funcName, params, ctx) {
    if (params.includes(null) || params.includes(undefined)) {
      return null;
    }
    const functionSymbol = symbolTable.getFunction(funcName, params);
    // 找不到匹配函数的错误
    // 实际上并不能保证不报错就能够找到
    // 因为实际运行中函数是根据参数类型动态查找的
    // 因为有着自定义类型的存在，参数类型也可能是动态的
    // 所以可能出现实际运行时反而找不到函数的情况
    if (functionSymbol === null) {
      this.errors.push(
        getError(
          ctx,
          "can't find function matched '" +
            funcName +
            "(" +
            params.map((x) => x?.type).join(",") +
            ")'"
        )
      );
      return null;
    }
    return Create(functionSymbol.returnType);
  };

  // Visit a parse tree produced by PL00Parser#expression.
  PL00QuickVisitor.prototype.visitExpression = function (ctx) {
    let children = this.visitChildren(ctx);
    let offset = 0;
    if (children.length === 1) {
      return children[0];
    }
    let result = new IntegerSymbol(0);
    if (children[0] !== undefined) {
      offset = 1;
      result = children[0];
    }
    const calculator = new PL00SymbolCalculator(this, symbolTable);
    // 错误恢复导致的null传播问题
    for (let i = 0; i * 2 + offset < children.length; ++i) {
      const rhs = children[i * 2 + offset + 1];
      if (result === null) {
        result = rhs;
        continue;
      }
      if (rhs === null) {
        continue;
      }
      if (ctx.children[i * 2 + offset].symbol.text == "+") {
        if (result === null) {
          result = rhs;
          continue;
        }
        result = calculator.Add(result, rhs);
      } else {
        if (result === null) {
          result = rhs;
          continue;
        }
        result = calculator.Sub(result, rhs);
      }
    }
    return result;
  };

  // Visit a parse tree produced by PL00Parser#term.
  PL00QuickVisitor.prototype.visitTerm = function (ctx) {
    let children = this.visitChildren(ctx);
    let result = children[0];
    const calculator = new PL00SymbolCalculator(this, symbolTable);
    for (let i = 0; i * 2 + 1 < children.length; i++) {
      const rhs = children[i * 2 + 2];
      if (result === null) {
        result = rhs;
        continue;
      }
      if (rhs === null) {
        continue;
      }
      if (ctx.children[i * 2 + 1].symbol.text == "*") {
        result = calculator.Multiply(result, rhs);
      } else {
        result = calculator.Divide(result, rhs);
      }
    }
    return result;
  };

  // Visit a parse tree produced by PL00Parser#IntegerFactor.
  PL00QuickVisitor.prototype.visitIntegerFactor = function (ctx) {
    return this.visitChildren(ctx)[0];
  };

  // Visit a parse tree produced by PL00Parser#BracketExpr.
  PL00QuickVisitor.prototype.visitBracketExpr = function (ctx) {
    return this.visitExpression(ctx.children[1]);
  };

  // Visit a parse tree produced by PL00Parser#Index.
  PL00QuickVisitor.prototype.visitIndex = function (ctx) {
    // 这里尤其坑，因为自定义类型求索引是无法静态确定类型的
    const children = this.visitChildren(ctx);
    const factor = children[0];
    if (factor.type === "String") {
      return new StringSymbol("");
    } else {
      if (children[2] === null) {
        return null;
      }
      return null;
    }
  };

  // Visit a parse tree produced by PL00Parser#StringFactor.
  PL00QuickVisitor.prototype.visitStringFactor = function (ctx) {
    return this.visitChildren(ctx)[0];
  };

  // Visit a parse tree produced by PL00Parser#FunctionCall.
  PL00QuickVisitor.prototype.visitFunctionCall = function (ctx) {
    return this.visitChildren(ctx)[0];
  };

  // Visit a parse tree produced by PL00Parser#IdentFactor.
  PL00QuickVisitor.prototype.visitIdentFactor = function (ctx) {
    const identName = this.visitChildren(ctx)[0];
    const result = symbolTable.get(identName);
    // 找不到标识符的错误
    if (result === null) {
      this.errors.push(
        getError(ctx, "identity '" + identName + "' not declared")
      );
    }
    return result;
  };

  // Visit a parse tree produced by PL00Parser#RealFactor.
  PL00QuickVisitor.prototype.visitRealFactor = function (ctx) {
    return this.visitChildren(ctx)[0];
  };

  // Visit a parse tree produced by PL00Parser#FunctionChain.
  PL00QuickVisitor.prototype.visitFunctionChain = function (ctx) {
    const factor = ctx.children[0].accept(this);
    const result = this.visitCallfunction(ctx.children[2], factor);
    return result;
  };

  // Visit a parse tree produced by PL00Parser#type.
  PL00QuickVisitor.prototype.visitType = function (ctx) {
    const typeName = ctx.children[0].symbol.text;
    return typeName;
  };

  // Visit a parse tree produced by PL00Parser#ident.
  PL00QuickVisitor.prototype.visitIdent = function (ctx) {
    const identName = ctx.children[0].symbol.text;
    return identName;
  };

  // Visit a parse tree produced by PL00Parser#integer.
  PL00QuickVisitor.prototype.visitInteger = function (ctx) {
    const text = ctx.children[0].symbol.text;
    return new IntegerSymbol(parseInt(text));
  };

  // Visit a parse tree produced by PL00Parser#real.
  PL00QuickVisitor.prototype.visitReal = function (ctx) {
    const text1 = ctx.children[0].symbol.text;
    const text2 = ctx.children[2].symbol.text;
    return new RealSymbol(parseFloat(text1 + "." + text2));
  };

  // Visit a parse tree produced by PL00Parser#string.
  PL00QuickVisitor.prototype.visitString = function (ctx) {
    let text = ctx.children[0].symbol.text;
    text = text.substr(1, text.length - 2);
    return new StringSymbol(text);
  };

  return PL00QuickVisitor;
}

export { PL00QuickVisitorFactory };
